#!/usr/bin/env python

# Copyright (C) 2020  Matthew "strager" Glazar
# See end of file for extended copyright information.

import io
import pathlib
import typing
import unicodedata

BITS_PER_BYTE = 8


def main() -> None:
    python_unicodedata_version = [
        int(part) for part in unicodedata.unidata_version.split(".")
    ]
    assert python_unicodedata_version >= [
        13,
        0,
        0,
    ], f"Unicode version {unicodedata.unidata_version} is too old. Python is probably too old. Upgrade Python."

    test_is_id_start()
    test_is_id_continue()
    test_is_js_identifier_start()
    test_is_js_identifier_part()
    test_dump_bit_table()
    test_rstrip_tuple()

    max_code_point = 0x10FFFF

    chunk_size = 256  # Arbitrary. Found to produce the smallest tables.

    chunk_index_type = "std::uint8_t"
    max_chunk_index = (1 << 8) - 1

    output_path = pathlib.Path(__file__).parent / ".." / "src" / "lex-unicode.cpp"
    print(f"Creating {output_path} ...")
    with open(output_path, "wb") as output:
        output.write(
            b"""\
// Copyright (C) 2020  Matthew "strager" Glazar
// See end of file for extended copyright information.

// This file was generated by tools/generate-lex-unicode.

#include <cstdint>
#include <quick-lint-js/lex.h>

// clang-format off

namespace quick_lint_js {
"""
        )

        output.write(
            f"""\
static_assert(lexer::unicode_table_chunk_size == {chunk_size});
static_assert(std::is_same_v<lexer::unicode_table_chunk_index_type, {chunk_index_type}>);

""".encode(
                "utf-8"
            )
        )

        identifier_start_data = tuple(
            is_js_identifier_start(code_point)
            for code_point in range(max_code_point + 1)
        )
        identifier_part_data = tuple(
            is_js_identifier_part(code_point)
            for code_point in range(max_code_point + 1)
        )

        chunk_data_to_index: typing.Dict[typing.Tuple[bool, ...], int] = {}
        all_chunks: typing.List[bool] = []

        def see_chunk(chunk_data: typing.Tuple[bool, ...]) -> None:
            chunk_index = chunk_data_to_index.get(chunk_data)
            if chunk_index is None:
                chunk_index = len(chunk_data_to_index)
                chunk_data_to_index[chunk_data] = chunk_index
                all_chunks.extend(chunk_data)
            return chunk_index

        for data in (identifier_start_data, identifier_part_data):
            for c in chunk(data, chunk_size):
                see_chunk(c)
        zeros_chunk_index = see_chunk((False,) * chunk_size)

        assert len(chunk_data_to_index) <= max_chunk_index

        output.write(b"const std::uint8_t lexer::unicode_tables_chunks[] = {\n")
        dump_bit_table_to_file(all_chunks, output, indentation=b"  ")
        print(f"unicode_tables_chunks_size = {len(all_chunks):}")
        output.write(b"\n};\n\n")

        def dump_chunk_indexes_table(name: str, data: typing.Tuple[bool, ...]) -> None:
            output.write(
                f"const lexer::unicode_table_chunk_index_type lexer::{name}[] = {{\n".encode(
                    "utf-8"
                )
            )
            chunk_indexes = tuple(
                chunk_data_to_index[c] for c in chunk(data, chunk_size)
            )
            chunk_indexes = rstrip_tuple(chunk_indexes, zeros_chunk_index)
            dump_integer_table_to_file(chunk_indexes, file=output, indentation=b"  ")
            print(f"{name}_size = {len(chunk_indexes)}")
            output.write(b"\n};\n")
            output.write(
                f"static_assert(lexer::{name}_size == sizeof(lexer::{name}));\n".encode(
                    "utf-8"
                )
            )

        dump_chunk_indexes_table(
            "identifier_start_chunk_indexes", identifier_start_data
        )
        output.write(b"\n")
        dump_chunk_indexes_table("identifier_part_chunk_indexes", identifier_part_data)

        output.write(b"}\n")

        output.write(
            b"""
// quick-lint-js finds bugs in JavaScript programs.
// Copyright (C) 2020  Matthew "strager" Glazar
//
// This file is part of quick-lint-js.
//
// quick-lint-js is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// quick-lint-js is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with quick-lint-js.  If not, see <https://www.gnu.org/licenses/>.
"""
        )


# http://www.ecma-international.org/ecma-262/11.0/index.html#prod-IdentifierStart
def is_js_identifier_start(code_point: int) -> bool:
    return code_point in (0x24, 0x5F) or is_id_start(code_point)


# http://www.ecma-international.org/ecma-262/11.0/index.html#prod-IdentifierPart
def is_js_identifier_part(code_point: int) -> bool:
    return code_point in (0x24, 0x200C, 0x200D) or is_id_continue(code_point)


# https://www.unicode.org/reports/tr31/tr31-33.html
def is_id_start(code_point: int) -> bool:
    category = unicodedata.category(chr(code_point))
    return (
        (
            category[0] == "L"
            or category == "Nl"
            or code_point in OTHER_ID_START_CODE_POINTS
        )
        and code_point not in PATTERN_SYNTAX_CODE_POINTS
        and code_point not in PATTERN_WHITE_SPACE_CODE_POINTS
    )


# https://www.unicode.org/reports/tr31/tr31-33.html
def is_id_continue(code_point: int) -> bool:
    category = unicodedata.category(chr(code_point))
    return (
        is_id_start(code_point)
        or category in ("Mc", "Mn", "Nd", "Pc")
        or code_point in OTHER_ID_CONTINUE_CODE_POINTS
    )


# Pattern_White_Space
# https://www.unicode.org/Public/11.0.0/ucd/PropList.txt
PATTERN_WHITE_SPACE_CODE_POINTS = frozenset(
    (
        0x0009,
        0x000A,
        0x000B,
        0x000C,
        0x000D,
        0x0020,
        0x0085,
        0x200E,
        0x200F,
        0x2028,
        0x2029,
    )
)

# Other_ID_Start
# https://www.unicode.org/Public/11.0.0/ucd/PropList.txt
OTHER_ID_START_CODE_POINTS = frozenset(
    (
        0x1885,
        0x1886,
        0x2118,
        0x212E,
        0x309B,
        0x309C,
    )
)

# Other_ID_Continue
# https://www.unicode.org/Public/11.0.0/ucd/PropList.txt
OTHER_ID_CONTINUE_CODE_POINTS = frozenset(
    (
        0x00B7,
        0x0387,
        0x1369,
        0x136A,
        0x136B,
        0x136C,
        0x136D,
        0x136E,
        0x136F,
        0x1370,
        0x1371,
        0x19DA,
    )
)

# Pattern_Syntax
# https://www.unicode.org/Public/11.0.0/ucd/PropList.txt
# fmt: off
PATTERN_SYNTAX_CODE_POINTS = frozenset(
    (
        0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029,
        0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x003A, 0x003B, 0x003C,
        0x003D, 0x003E, 0x003F, 0x0040, 0x005B, 0x005C, 0x005D, 0x005E, 0x0060,
        0x007B, 0x007C, 0x007D, 0x007E, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5,
        0x00A6, 0x00A7, 0x00A9, 0x00AB, 0x00AC, 0x00AE, 0x00B0, 0x00B1, 0x00B6,
        0x00BB, 0x00BF, 0x00D7, 0x00F7, 0x2010, 0x2011, 0x2012, 0x2013, 0x2014,
        0x2015, 0x2016, 0x2017, 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D,
        0x201E, 0x201F, 0x2020, 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026,
        0x2027, 0x2030, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037,
        0x2038, 0x2039, 0x203A, 0x203B, 0x203C, 0x203D, 0x203E, 0x2041, 0x2042,
        0x2043, 0x2044, 0x2045, 0x2046, 0x2047, 0x2048, 0x2049, 0x204A, 0x204B,
        0x204C, 0x204D, 0x204E, 0x204F, 0x2050, 0x2051, 0x2052, 0x2053, 0x2055,
        0x2056, 0x2057, 0x2058, 0x2059, 0x205A, 0x205B, 0x205C, 0x205D, 0x205E,
        0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198,
        0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F, 0x21A0, 0x21A1,
        0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7, 0x21A8, 0x21A9, 0x21AA,
        0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF, 0x21B0, 0x21B1, 0x21B2, 0x21B3,
        0x21B4, 0x21B5, 0x21B6, 0x21B7, 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC,
        0x21BD, 0x21BE, 0x21BF, 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5,
        0x21C6, 0x21C7, 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE,
        0x21CF, 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7,
        0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF, 0x21E0,
        0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7, 0x21E8, 0x21E9,
        0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF, 0x21F0, 0x21F1, 0x21F2,
        0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7, 0x21F8, 0x21F9, 0x21FA, 0x21FB,
        0x21FC, 0x21FD, 0x21FE, 0x21FF, 0x2200, 0x2201, 0x2202, 0x2203, 0x2204,
        0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220A, 0x220B, 0x220C, 0x220D,
        0x220E, 0x220F, 0x2210, 0x2211, 0x2212, 0x2213, 0x2214, 0x2215, 0x2216,
        0x2217, 0x2218, 0x2219, 0x221A, 0x221B, 0x221C, 0x221D, 0x221E, 0x221F,
        0x2220, 0x2221, 0x2222, 0x2223, 0x2224, 0x2225, 0x2226, 0x2227, 0x2228,
        0x2229, 0x222A, 0x222B, 0x222C, 0x222D, 0x222E, 0x222F, 0x2230, 0x2231,
        0x2232, 0x2233, 0x2234, 0x2235, 0x2236, 0x2237, 0x2238, 0x2239, 0x223A,
        0x223B, 0x223C, 0x223D, 0x223E, 0x223F, 0x2240, 0x2241, 0x2242, 0x2243,
        0x2244, 0x2245, 0x2246, 0x2247, 0x2248, 0x2249, 0x224A, 0x224B, 0x224C,
        0x224D, 0x224E, 0x224F, 0x2250, 0x2251, 0x2252, 0x2253, 0x2254, 0x2255,
        0x2256, 0x2257, 0x2258, 0x2259, 0x225A, 0x225B, 0x225C, 0x225D, 0x225E,
        0x225F, 0x2260, 0x2261, 0x2262, 0x2263, 0x2264, 0x2265, 0x2266, 0x2267,
        0x2268, 0x2269, 0x226A, 0x226B, 0x226C, 0x226D, 0x226E, 0x226F, 0x2270,
        0x2271, 0x2272, 0x2273, 0x2274, 0x2275, 0x2276, 0x2277, 0x2278, 0x2279,
        0x227A, 0x227B, 0x227C, 0x227D, 0x227E, 0x227F, 0x2280, 0x2281, 0x2282,
        0x2283, 0x2284, 0x2285, 0x2286, 0x2287, 0x2288, 0x2289, 0x228A, 0x228B,
        0x228C, 0x228D, 0x228E, 0x228F, 0x2290, 0x2291, 0x2292, 0x2293, 0x2294,
        0x2295, 0x2296, 0x2297, 0x2298, 0x2299, 0x229A, 0x229B, 0x229C, 0x229D,
        0x229E, 0x229F, 0x22A0, 0x22A1, 0x22A2, 0x22A3, 0x22A4, 0x22A5, 0x22A6,
        0x22A7, 0x22A8, 0x22A9, 0x22AA, 0x22AB, 0x22AC, 0x22AD, 0x22AE, 0x22AF,
        0x22B0, 0x22B1, 0x22B2, 0x22B3, 0x22B4, 0x22B5, 0x22B6, 0x22B7, 0x22B8,
        0x22B9, 0x22BA, 0x22BB, 0x22BC, 0x22BD, 0x22BE, 0x22BF, 0x22C0, 0x22C1,
        0x22C2, 0x22C3, 0x22C4, 0x22C5, 0x22C6, 0x22C7, 0x22C8, 0x22C9, 0x22CA,
        0x22CB, 0x22CC, 0x22CD, 0x22CE, 0x22CF, 0x22D0, 0x22D1, 0x22D2, 0x22D3,
        0x22D4, 0x22D5, 0x22D6, 0x22D7, 0x22D8, 0x22D9, 0x22DA, 0x22DB, 0x22DC,
        0x22DD, 0x22DE, 0x22DF, 0x22E0, 0x22E1, 0x22E2, 0x22E3, 0x22E4, 0x22E5,
        0x22E6, 0x22E7, 0x22E8, 0x22E9, 0x22EA, 0x22EB, 0x22EC, 0x22ED, 0x22EE,
        0x22EF, 0x22F0, 0x22F1, 0x22F2, 0x22F3, 0x22F4, 0x22F5, 0x22F6, 0x22F7,
        0x22F8, 0x22F9, 0x22FA, 0x22FB, 0x22FC, 0x22FD, 0x22FE, 0x22FF, 0x2300,
        0x2301, 0x2302, 0x2303, 0x2304, 0x2305, 0x2306, 0x2307, 0x2308, 0x2309,
        0x230A, 0x230B, 0x230C, 0x230D, 0x230E, 0x230F, 0x2310, 0x2311, 0x2312,
        0x2313, 0x2314, 0x2315, 0x2316, 0x2317, 0x2318, 0x2319, 0x231A, 0x231B,
        0x231C, 0x231D, 0x231E, 0x231F, 0x2320, 0x2321, 0x2322, 0x2323, 0x2324,
        0x2325, 0x2326, 0x2327, 0x2328, 0x2329, 0x232A, 0x232B, 0x232C, 0x232D,
        0x232E, 0x232F, 0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336,
        0x2337, 0x2338, 0x2339, 0x233A, 0x233B, 0x233C, 0x233D, 0x233E, 0x233F,
        0x2340, 0x2341, 0x2342, 0x2343, 0x2344, 0x2345, 0x2346, 0x2347, 0x2348,
        0x2349, 0x234A, 0x234B, 0x234C, 0x234D, 0x234E, 0x234F, 0x2350, 0x2351,
        0x2352, 0x2353, 0x2354, 0x2355, 0x2356, 0x2357, 0x2358, 0x2359, 0x235A,
        0x235B, 0x235C, 0x235D, 0x235E, 0x235F, 0x2360, 0x2361, 0x2362, 0x2363,
        0x2364, 0x2365, 0x2366, 0x2367, 0x2368, 0x2369, 0x236A, 0x236B, 0x236C,
        0x236D, 0x236E, 0x236F, 0x2370, 0x2371, 0x2372, 0x2373, 0x2374, 0x2375,
        0x2376, 0x2377, 0x2378, 0x2379, 0x237A, 0x237B, 0x237C, 0x237D, 0x237E,
        0x237F, 0x2380, 0x2381, 0x2382, 0x2383, 0x2384, 0x2385, 0x2386, 0x2387,
        0x2388, 0x2389, 0x238A, 0x238B, 0x238C, 0x238D, 0x238E, 0x238F, 0x2390,
        0x2391, 0x2392, 0x2393, 0x2394, 0x2395, 0x2396, 0x2397, 0x2398, 0x2399,
        0x239A, 0x239B, 0x239C, 0x239D, 0x239E, 0x239F, 0x23A0, 0x23A1, 0x23A2,
        0x23A3, 0x23A4, 0x23A5, 0x23A6, 0x23A7, 0x23A8, 0x23A9, 0x23AA, 0x23AB,
        0x23AC, 0x23AD, 0x23AE, 0x23AF, 0x23B0, 0x23B1, 0x23B2, 0x23B3, 0x23B4,
        0x23B5, 0x23B6, 0x23B7, 0x23B8, 0x23B9, 0x23BA, 0x23BB, 0x23BC, 0x23BD,
        0x23BE, 0x23BF, 0x23C0, 0x23C1, 0x23C2, 0x23C3, 0x23C4, 0x23C5, 0x23C6,
        0x23C7, 0x23C8, 0x23C9, 0x23CA, 0x23CB, 0x23CC, 0x23CD, 0x23CE, 0x23CF,
        0x23D0, 0x23D1, 0x23D2, 0x23D3, 0x23D4, 0x23D5, 0x23D6, 0x23D7, 0x23D8,
        0x23D9, 0x23DA, 0x23DB, 0x23DC, 0x23DD, 0x23DE, 0x23DF, 0x23E0, 0x23E1,
        0x23E2, 0x23E3, 0x23E4, 0x23E5, 0x23E6, 0x23E7, 0x23E8, 0x23E9, 0x23EA,
        0x23EB, 0x23EC, 0x23ED, 0x23EE, 0x23EF, 0x23F0, 0x23F1, 0x23F2, 0x23F3,
        0x23F4, 0x23F5, 0x23F6, 0x23F7, 0x23F8, 0x23F9, 0x23FA, 0x23FB, 0x23FC,
        0x23FD, 0x23FE, 0x23FF, 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405,
        0x2406, 0x2407, 0x2408, 0x2409, 0x240A, 0x240B, 0x240C, 0x240D, 0x240E,
        0x240F, 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417,
        0x2418, 0x2419, 0x241A, 0x241B, 0x241C, 0x241D, 0x241E, 0x241F, 0x2420,
        0x2421, 0x2422, 0x2423, 0x2424, 0x2425, 0x2426, 0x2427, 0x2428, 0x2429,
        0x242A, 0x242B, 0x242C, 0x242D, 0x242E, 0x242F, 0x2430, 0x2431, 0x2432,
        0x2433, 0x2434, 0x2435, 0x2436, 0x2437, 0x2438, 0x2439, 0x243A, 0x243B,
        0x243C, 0x243D, 0x243E, 0x243F, 0x2440, 0x2441, 0x2442, 0x2443, 0x2444,
        0x2445, 0x2446, 0x2447, 0x2448, 0x2449, 0x244A, 0x244B, 0x244C, 0x244D,
        0x244E, 0x244F, 0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456,
        0x2457, 0x2458, 0x2459, 0x245A, 0x245B, 0x245C, 0x245D, 0x245E, 0x245F,
        0x2500, 0x2501, 0x2502, 0x2503, 0x2504, 0x2505, 0x2506, 0x2507, 0x2508,
        0x2509, 0x250A, 0x250B, 0x250C, 0x250D, 0x250E, 0x250F, 0x2510, 0x2511,
        0x2512, 0x2513, 0x2514, 0x2515, 0x2516, 0x2517, 0x2518, 0x2519, 0x251A,
        0x251B, 0x251C, 0x251D, 0x251E, 0x251F, 0x2520, 0x2521, 0x2522, 0x2523,
        0x2524, 0x2525, 0x2526, 0x2527, 0x2528, 0x2529, 0x252A, 0x252B, 0x252C,
        0x252D, 0x252E, 0x252F, 0x2530, 0x2531, 0x2532, 0x2533, 0x2534, 0x2535,
        0x2536, 0x2537, 0x2538, 0x2539, 0x253A, 0x253B, 0x253C, 0x253D, 0x253E,
        0x253F, 0x2540, 0x2541, 0x2542, 0x2543, 0x2544, 0x2545, 0x2546, 0x2547,
        0x2548, 0x2549, 0x254A, 0x254B, 0x254C, 0x254D, 0x254E, 0x254F, 0x2550,
        0x2551, 0x2552, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559,
        0x255A, 0x255B, 0x255C, 0x255D, 0x255E, 0x255F, 0x2560, 0x2561, 0x2562,
        0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B,
        0x256C, 0x256D, 0x256E, 0x256F, 0x2570, 0x2571, 0x2572, 0x2573, 0x2574,
        0x2575, 0x2576, 0x2577, 0x2578, 0x2579, 0x257A, 0x257B, 0x257C, 0x257D,
        0x257E, 0x257F, 0x2580, 0x2581, 0x2582, 0x2583, 0x2584, 0x2585, 0x2586,
        0x2587, 0x2588, 0x2589, 0x258A, 0x258B, 0x258C, 0x258D, 0x258E, 0x258F,
        0x2590, 0x2591, 0x2592, 0x2593, 0x2594, 0x2595, 0x2596, 0x2597, 0x2598,
        0x2599, 0x259A, 0x259B, 0x259C, 0x259D, 0x259E, 0x259F, 0x25A0, 0x25A1,
        0x25A2, 0x25A3, 0x25A4, 0x25A5, 0x25A6, 0x25A7, 0x25A8, 0x25A9, 0x25AA,
        0x25AB, 0x25AC, 0x25AD, 0x25AE, 0x25AF, 0x25B0, 0x25B1, 0x25B2, 0x25B3,
        0x25B4, 0x25B5, 0x25B6, 0x25B7, 0x25B8, 0x25B9, 0x25BA, 0x25BB, 0x25BC,
        0x25BD, 0x25BE, 0x25BF, 0x25C0, 0x25C1, 0x25C2, 0x25C3, 0x25C4, 0x25C5,
        0x25C6, 0x25C7, 0x25C8, 0x25C9, 0x25CA, 0x25CB, 0x25CC, 0x25CD, 0x25CE,
        0x25CF, 0x25D0, 0x25D1, 0x25D2, 0x25D3, 0x25D4, 0x25D5, 0x25D6, 0x25D7,
        0x25D8, 0x25D9, 0x25DA, 0x25DB, 0x25DC, 0x25DD, 0x25DE, 0x25DF, 0x25E0,
        0x25E1, 0x25E2, 0x25E3, 0x25E4, 0x25E5, 0x25E6, 0x25E7, 0x25E8, 0x25E9,
        0x25EA, 0x25EB, 0x25EC, 0x25ED, 0x25EE, 0x25EF, 0x25F0, 0x25F1, 0x25F2,
        0x25F3, 0x25F4, 0x25F5, 0x25F6, 0x25F7, 0x25F8, 0x25F9, 0x25FA, 0x25FB,
        0x25FC, 0x25FD, 0x25FE, 0x25FF, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604,
        0x2605, 0x2606, 0x2607, 0x2608, 0x2609, 0x260A, 0x260B, 0x260C, 0x260D,
        0x260E, 0x260F, 0x2610, 0x2611, 0x2612, 0x2613, 0x2614, 0x2615, 0x2616,
        0x2617, 0x2618, 0x2619, 0x261A, 0x261B, 0x261C, 0x261D, 0x261E, 0x261F,
        0x2620, 0x2621, 0x2622, 0x2623, 0x2624, 0x2625, 0x2626, 0x2627, 0x2628,
        0x2629, 0x262A, 0x262B, 0x262C, 0x262D, 0x262E, 0x262F, 0x2630, 0x2631,
        0x2632, 0x2633, 0x2634, 0x2635, 0x2636, 0x2637, 0x2638, 0x2639, 0x263A,
        0x263B, 0x263C, 0x263D, 0x263E, 0x263F, 0x2640, 0x2641, 0x2642, 0x2643,
        0x2644, 0x2645, 0x2646, 0x2647, 0x2648, 0x2649, 0x264A, 0x264B, 0x264C,
        0x264D, 0x264E, 0x264F, 0x2650, 0x2651, 0x2652, 0x2653, 0x2654, 0x2655,
        0x2656, 0x2657, 0x2658, 0x2659, 0x265A, 0x265B, 0x265C, 0x265D, 0x265E,
        0x265F, 0x2660, 0x2661, 0x2662, 0x2663, 0x2664, 0x2665, 0x2666, 0x2667,
        0x2668, 0x2669, 0x266A, 0x266B, 0x266C, 0x266D, 0x266E, 0x266F, 0x2670,
        0x2671, 0x2672, 0x2673, 0x2674, 0x2675, 0x2676, 0x2677, 0x2678, 0x2679,
        0x267A, 0x267B, 0x267C, 0x267D, 0x267E, 0x267F, 0x2680, 0x2681, 0x2682,
        0x2683, 0x2684, 0x2685, 0x2686, 0x2687, 0x2688, 0x2689, 0x268A, 0x268B,
        0x268C, 0x268D, 0x268E, 0x268F, 0x2690, 0x2691, 0x2692, 0x2693, 0x2694,
        0x2695, 0x2696, 0x2697, 0x2698, 0x2699, 0x269A, 0x269B, 0x269C, 0x269D,
        0x269E, 0x269F, 0x26A0, 0x26A1, 0x26A2, 0x26A3, 0x26A4, 0x26A5, 0x26A6,
        0x26A7, 0x26A8, 0x26A9, 0x26AA, 0x26AB, 0x26AC, 0x26AD, 0x26AE, 0x26AF,
        0x26B0, 0x26B1, 0x26B2, 0x26B3, 0x26B4, 0x26B5, 0x26B6, 0x26B7, 0x26B8,
        0x26B9, 0x26BA, 0x26BB, 0x26BC, 0x26BD, 0x26BE, 0x26BF, 0x26C0, 0x26C1,
        0x26C2, 0x26C3, 0x26C4, 0x26C5, 0x26C6, 0x26C7, 0x26C8, 0x26C9, 0x26CA,
        0x26CB, 0x26CC, 0x26CD, 0x26CE, 0x26CF, 0x26D0, 0x26D1, 0x26D2, 0x26D3,
        0x26D4, 0x26D5, 0x26D6, 0x26D7, 0x26D8, 0x26D9, 0x26DA, 0x26DB, 0x26DC,
        0x26DD, 0x26DE, 0x26DF, 0x26E0, 0x26E1, 0x26E2, 0x26E3, 0x26E4, 0x26E5,
        0x26E6, 0x26E7, 0x26E8, 0x26E9, 0x26EA, 0x26EB, 0x26EC, 0x26ED, 0x26EE,
        0x26EF, 0x26F0, 0x26F1, 0x26F2, 0x26F3, 0x26F4, 0x26F5, 0x26F6, 0x26F7,
        0x26F8, 0x26F9, 0x26FA, 0x26FB, 0x26FC, 0x26FD, 0x26FE, 0x26FF, 0x2700,
        0x2701, 0x2702, 0x2703, 0x2704, 0x2705, 0x2706, 0x2707, 0x2708, 0x2709,
        0x270A, 0x270B, 0x270C, 0x270D, 0x270E, 0x270F, 0x2710, 0x2711, 0x2712,
        0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719, 0x271A, 0x271B,
        0x271C, 0x271D, 0x271E, 0x271F, 0x2720, 0x2721, 0x2722, 0x2723, 0x2724,
        0x2725, 0x2726, 0x2727, 0x2728, 0x2729, 0x272A, 0x272B, 0x272C, 0x272D,
        0x272E, 0x272F, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736,
        0x2737, 0x2738, 0x2739, 0x273A, 0x273B, 0x273C, 0x273D, 0x273E, 0x273F,
        0x2740, 0x2741, 0x2742, 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748,
        0x2749, 0x274A, 0x274B, 0x274C, 0x274D, 0x274E, 0x274F, 0x2750, 0x2751,
        0x2752, 0x2753, 0x2754, 0x2755, 0x2756, 0x2757, 0x2758, 0x2759, 0x275A,
        0x275B, 0x275C, 0x275D, 0x275E, 0x275F, 0x2760, 0x2761, 0x2762, 0x2763,
        0x2764, 0x2765, 0x2766, 0x2767, 0x2768, 0x2769, 0x276A, 0x276B, 0x276C,
        0x276D, 0x276E, 0x276F, 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, 0x2775,
        0x2794, 0x2795, 0x2796, 0x2797, 0x2798, 0x2799, 0x279A, 0x279B, 0x279C,
        0x279D, 0x279E, 0x279F, 0x27A0, 0x27A1, 0x27A2, 0x27A3, 0x27A4, 0x27A5,
        0x27A6, 0x27A7, 0x27A8, 0x27A9, 0x27AA, 0x27AB, 0x27AC, 0x27AD, 0x27AE,
        0x27AF, 0x27B0, 0x27B1, 0x27B2, 0x27B3, 0x27B4, 0x27B5, 0x27B6, 0x27B7,
        0x27B8, 0x27B9, 0x27BA, 0x27BB, 0x27BC, 0x27BD, 0x27BE, 0x27BF, 0x27C0,
        0x27C1, 0x27C2, 0x27C3, 0x27C4, 0x27C5, 0x27C6, 0x27C7, 0x27C8, 0x27C9,
        0x27CA, 0x27CB, 0x27CC, 0x27CD, 0x27CE, 0x27CF, 0x27D0, 0x27D1, 0x27D2,
        0x27D3, 0x27D4, 0x27D5, 0x27D6, 0x27D7, 0x27D8, 0x27D9, 0x27DA, 0x27DB,
        0x27DC, 0x27DD, 0x27DE, 0x27DF, 0x27E0, 0x27E1, 0x27E2, 0x27E3, 0x27E4,
        0x27E5, 0x27E6, 0x27E7, 0x27E8, 0x27E9, 0x27EA, 0x27EB, 0x27EC, 0x27ED,
        0x27EE, 0x27EF, 0x27F0, 0x27F1, 0x27F2, 0x27F3, 0x27F4, 0x27F5, 0x27F6,
        0x27F7, 0x27F8, 0x27F9, 0x27FA, 0x27FB, 0x27FC, 0x27FD, 0x27FE, 0x27FF,
        0x2800, 0x2801, 0x2802, 0x2803, 0x2804, 0x2805, 0x2806, 0x2807, 0x2808,
        0x2809, 0x280A, 0x280B, 0x280C, 0x280D, 0x280E, 0x280F, 0x2810, 0x2811,
        0x2812, 0x2813, 0x2814, 0x2815, 0x2816, 0x2817, 0x2818, 0x2819, 0x281A,
        0x281B, 0x281C, 0x281D, 0x281E, 0x281F, 0x2820, 0x2821, 0x2822, 0x2823,
        0x2824, 0x2825, 0x2826, 0x2827, 0x2828, 0x2829, 0x282A, 0x282B, 0x282C,
        0x282D, 0x282E, 0x282F, 0x2830, 0x2831, 0x2832, 0x2833, 0x2834, 0x2835,
        0x2836, 0x2837, 0x2838, 0x2839, 0x283A, 0x283B, 0x283C, 0x283D, 0x283E,
        0x283F, 0x2840, 0x2841, 0x2842, 0x2843, 0x2844, 0x2845, 0x2846, 0x2847,
        0x2848, 0x2849, 0x284A, 0x284B, 0x284C, 0x284D, 0x284E, 0x284F, 0x2850,
        0x2851, 0x2852, 0x2853, 0x2854, 0x2855, 0x2856, 0x2857, 0x2858, 0x2859,
        0x285A, 0x285B, 0x285C, 0x285D, 0x285E, 0x285F, 0x2860, 0x2861, 0x2862,
        0x2863, 0x2864, 0x2865, 0x2866, 0x2867, 0x2868, 0x2869, 0x286A, 0x286B,
        0x286C, 0x286D, 0x286E, 0x286F, 0x2870, 0x2871, 0x2872, 0x2873, 0x2874,
        0x2875, 0x2876, 0x2877, 0x2878, 0x2879, 0x287A, 0x287B, 0x287C, 0x287D,
        0x287E, 0x287F, 0x2880, 0x2881, 0x2882, 0x2883, 0x2884, 0x2885, 0x2886,
        0x2887, 0x2888, 0x2889, 0x288A, 0x288B, 0x288C, 0x288D, 0x288E, 0x288F,
        0x2890, 0x2891, 0x2892, 0x2893, 0x2894, 0x2895, 0x2896, 0x2897, 0x2898,
        0x2899, 0x289A, 0x289B, 0x289C, 0x289D, 0x289E, 0x289F, 0x28A0, 0x28A1,
        0x28A2, 0x28A3, 0x28A4, 0x28A5, 0x28A6, 0x28A7, 0x28A8, 0x28A9, 0x28AA,
        0x28AB, 0x28AC, 0x28AD, 0x28AE, 0x28AF, 0x28B0, 0x28B1, 0x28B2, 0x28B3,
        0x28B4, 0x28B5, 0x28B6, 0x28B7, 0x28B8, 0x28B9, 0x28BA, 0x28BB, 0x28BC,
        0x28BD, 0x28BE, 0x28BF, 0x28C0, 0x28C1, 0x28C2, 0x28C3, 0x28C4, 0x28C5,
        0x28C6, 0x28C7, 0x28C8, 0x28C9, 0x28CA, 0x28CB, 0x28CC, 0x28CD, 0x28CE,
        0x28CF, 0x28D0, 0x28D1, 0x28D2, 0x28D3, 0x28D4, 0x28D5, 0x28D6, 0x28D7,
        0x28D8, 0x28D9, 0x28DA, 0x28DB, 0x28DC, 0x28DD, 0x28DE, 0x28DF, 0x28E0,
        0x28E1, 0x28E2, 0x28E3, 0x28E4, 0x28E5, 0x28E6, 0x28E7, 0x28E8, 0x28E9,
        0x28EA, 0x28EB, 0x28EC, 0x28ED, 0x28EE, 0x28EF, 0x28F0, 0x28F1, 0x28F2,
        0x28F3, 0x28F4, 0x28F5, 0x28F6, 0x28F7, 0x28F8, 0x28F9, 0x28FA, 0x28FB,
        0x28FC, 0x28FD, 0x28FE, 0x28FF, 0x2900, 0x2901, 0x2902, 0x2903, 0x2904,
        0x2905, 0x2906, 0x2907, 0x2908, 0x2909, 0x290A, 0x290B, 0x290C, 0x290D,
        0x290E, 0x290F, 0x2910, 0x2911, 0x2912, 0x2913, 0x2914, 0x2915, 0x2916,
        0x2917, 0x2918, 0x2919, 0x291A, 0x291B, 0x291C, 0x291D, 0x291E, 0x291F,
        0x2920, 0x2921, 0x2922, 0x2923, 0x2924, 0x2925, 0x2926, 0x2927, 0x2928,
        0x2929, 0x292A, 0x292B, 0x292C, 0x292D, 0x292E, 0x292F, 0x2930, 0x2931,
        0x2932, 0x2933, 0x2934, 0x2935, 0x2936, 0x2937, 0x2938, 0x2939, 0x293A,
        0x293B, 0x293C, 0x293D, 0x293E, 0x293F, 0x2940, 0x2941, 0x2942, 0x2943,
        0x2944, 0x2945, 0x2946, 0x2947, 0x2948, 0x2949, 0x294A, 0x294B, 0x294C,
        0x294D, 0x294E, 0x294F, 0x2950, 0x2951, 0x2952, 0x2953, 0x2954, 0x2955,
        0x2956, 0x2957, 0x2958, 0x2959, 0x295A, 0x295B, 0x295C, 0x295D, 0x295E,
        0x295F, 0x2960, 0x2961, 0x2962, 0x2963, 0x2964, 0x2965, 0x2966, 0x2967,
        0x2968, 0x2969, 0x296A, 0x296B, 0x296C, 0x296D, 0x296E, 0x296F, 0x2970,
        0x2971, 0x2972, 0x2973, 0x2974, 0x2975, 0x2976, 0x2977, 0x2978, 0x2979,
        0x297A, 0x297B, 0x297C, 0x297D, 0x297E, 0x297F, 0x2980, 0x2981, 0x2982,
        0x2983, 0x2984, 0x2985, 0x2986, 0x2987, 0x2988, 0x2989, 0x298A, 0x298B,
        0x298C, 0x298D, 0x298E, 0x298F, 0x2990, 0x2991, 0x2992, 0x2993, 0x2994,
        0x2995, 0x2996, 0x2997, 0x2998, 0x2999, 0x299A, 0x299B, 0x299C, 0x299D,
        0x299E, 0x299F, 0x29A0, 0x29A1, 0x29A2, 0x29A3, 0x29A4, 0x29A5, 0x29A6,
        0x29A7, 0x29A8, 0x29A9, 0x29AA, 0x29AB, 0x29AC, 0x29AD, 0x29AE, 0x29AF,
        0x29B0, 0x29B1, 0x29B2, 0x29B3, 0x29B4, 0x29B5, 0x29B6, 0x29B7, 0x29B8,
        0x29B9, 0x29BA, 0x29BB, 0x29BC, 0x29BD, 0x29BE, 0x29BF, 0x29C0, 0x29C1,
        0x29C2, 0x29C3, 0x29C4, 0x29C5, 0x29C6, 0x29C7, 0x29C8, 0x29C9, 0x29CA,
        0x29CB, 0x29CC, 0x29CD, 0x29CE, 0x29CF, 0x29D0, 0x29D1, 0x29D2, 0x29D3,
        0x29D4, 0x29D5, 0x29D6, 0x29D7, 0x29D8, 0x29D9, 0x29DA, 0x29DB, 0x29DC,
        0x29DD, 0x29DE, 0x29DF, 0x29E0, 0x29E1, 0x29E2, 0x29E3, 0x29E4, 0x29E5,
        0x29E6, 0x29E7, 0x29E8, 0x29E9, 0x29EA, 0x29EB, 0x29EC, 0x29ED, 0x29EE,
        0x29EF, 0x29F0, 0x29F1, 0x29F2, 0x29F3, 0x29F4, 0x29F5, 0x29F6, 0x29F7,
        0x29F8, 0x29F9, 0x29FA, 0x29FB, 0x29FC, 0x29FD, 0x29FE, 0x29FF, 0x2A00,
        0x2A01, 0x2A02, 0x2A03, 0x2A04, 0x2A05, 0x2A06, 0x2A07, 0x2A08, 0x2A09,
        0x2A0A, 0x2A0B, 0x2A0C, 0x2A0D, 0x2A0E, 0x2A0F, 0x2A10, 0x2A11, 0x2A12,
        0x2A13, 0x2A14, 0x2A15, 0x2A16, 0x2A17, 0x2A18, 0x2A19, 0x2A1A, 0x2A1B,
        0x2A1C, 0x2A1D, 0x2A1E, 0x2A1F, 0x2A20, 0x2A21, 0x2A22, 0x2A23, 0x2A24,
        0x2A25, 0x2A26, 0x2A27, 0x2A28, 0x2A29, 0x2A2A, 0x2A2B, 0x2A2C, 0x2A2D,
        0x2A2E, 0x2A2F, 0x2A30, 0x2A31, 0x2A32, 0x2A33, 0x2A34, 0x2A35, 0x2A36,
        0x2A37, 0x2A38, 0x2A39, 0x2A3A, 0x2A3B, 0x2A3C, 0x2A3D, 0x2A3E, 0x2A3F,
        0x2A40, 0x2A41, 0x2A42, 0x2A43, 0x2A44, 0x2A45, 0x2A46, 0x2A47, 0x2A48,
        0x2A49, 0x2A4A, 0x2A4B, 0x2A4C, 0x2A4D, 0x2A4E, 0x2A4F, 0x2A50, 0x2A51,
        0x2A52, 0x2A53, 0x2A54, 0x2A55, 0x2A56, 0x2A57, 0x2A58, 0x2A59, 0x2A5A,
        0x2A5B, 0x2A5C, 0x2A5D, 0x2A5E, 0x2A5F, 0x2A60, 0x2A61, 0x2A62, 0x2A63,
        0x2A64, 0x2A65, 0x2A66, 0x2A67, 0x2A68, 0x2A69, 0x2A6A, 0x2A6B, 0x2A6C,
        0x2A6D, 0x2A6E, 0x2A6F, 0x2A70, 0x2A71, 0x2A72, 0x2A73, 0x2A74, 0x2A75,
        0x2A76, 0x2A77, 0x2A78, 0x2A79, 0x2A7A, 0x2A7B, 0x2A7C, 0x2A7D, 0x2A7E,
        0x2A7F, 0x2A80, 0x2A81, 0x2A82, 0x2A83, 0x2A84, 0x2A85, 0x2A86, 0x2A87,
        0x2A88, 0x2A89, 0x2A8A, 0x2A8B, 0x2A8C, 0x2A8D, 0x2A8E, 0x2A8F, 0x2A90,
        0x2A91, 0x2A92, 0x2A93, 0x2A94, 0x2A95, 0x2A96, 0x2A97, 0x2A98, 0x2A99,
        0x2A9A, 0x2A9B, 0x2A9C, 0x2A9D, 0x2A9E, 0x2A9F, 0x2AA0, 0x2AA1, 0x2AA2,
        0x2AA3, 0x2AA4, 0x2AA5, 0x2AA6, 0x2AA7, 0x2AA8, 0x2AA9, 0x2AAA, 0x2AAB,
        0x2AAC, 0x2AAD, 0x2AAE, 0x2AAF, 0x2AB0, 0x2AB1, 0x2AB2, 0x2AB3, 0x2AB4,
        0x2AB5, 0x2AB6, 0x2AB7, 0x2AB8, 0x2AB9, 0x2ABA, 0x2ABB, 0x2ABC, 0x2ABD,
        0x2ABE, 0x2ABF, 0x2AC0, 0x2AC1, 0x2AC2, 0x2AC3, 0x2AC4, 0x2AC5, 0x2AC6,
        0x2AC7, 0x2AC8, 0x2AC9, 0x2ACA, 0x2ACB, 0x2ACC, 0x2ACD, 0x2ACE, 0x2ACF,
        0x2AD0, 0x2AD1, 0x2AD2, 0x2AD3, 0x2AD4, 0x2AD5, 0x2AD6, 0x2AD7, 0x2AD8,
        0x2AD9, 0x2ADA, 0x2ADB, 0x2ADC, 0x2ADD, 0x2ADE, 0x2ADF, 0x2AE0, 0x2AE1,
        0x2AE2, 0x2AE3, 0x2AE4, 0x2AE5, 0x2AE6, 0x2AE7, 0x2AE8, 0x2AE9, 0x2AEA,
        0x2AEB, 0x2AEC, 0x2AED, 0x2AEE, 0x2AEF, 0x2AF0, 0x2AF1, 0x2AF2, 0x2AF3,
        0x2AF4, 0x2AF5, 0x2AF6, 0x2AF7, 0x2AF8, 0x2AF9, 0x2AFA, 0x2AFB, 0x2AFC,
        0x2AFD, 0x2AFE, 0x2AFF, 0x2B00, 0x2B01, 0x2B02, 0x2B03, 0x2B04, 0x2B05,
        0x2B06, 0x2B07, 0x2B08, 0x2B09, 0x2B0A, 0x2B0B, 0x2B0C, 0x2B0D, 0x2B0E,
        0x2B0F, 0x2B10, 0x2B11, 0x2B12, 0x2B13, 0x2B14, 0x2B15, 0x2B16, 0x2B17,
        0x2B18, 0x2B19, 0x2B1A, 0x2B1B, 0x2B1C, 0x2B1D, 0x2B1E, 0x2B1F, 0x2B20,
        0x2B21, 0x2B22, 0x2B23, 0x2B24, 0x2B25, 0x2B26, 0x2B27, 0x2B28, 0x2B29,
        0x2B2A, 0x2B2B, 0x2B2C, 0x2B2D, 0x2B2E, 0x2B2F, 0x2B30, 0x2B31, 0x2B32,
        0x2B33, 0x2B34, 0x2B35, 0x2B36, 0x2B37, 0x2B38, 0x2B39, 0x2B3A, 0x2B3B,
        0x2B3C, 0x2B3D, 0x2B3E, 0x2B3F, 0x2B40, 0x2B41, 0x2B42, 0x2B43, 0x2B44,
        0x2B45, 0x2B46, 0x2B47, 0x2B48, 0x2B49, 0x2B4A, 0x2B4B, 0x2B4C, 0x2B4D,
        0x2B4E, 0x2B4F, 0x2B50, 0x2B51, 0x2B52, 0x2B53, 0x2B54, 0x2B55, 0x2B56,
        0x2B57, 0x2B58, 0x2B59, 0x2B5A, 0x2B5B, 0x2B5C, 0x2B5D, 0x2B5E, 0x2B5F,
        0x2B60, 0x2B61, 0x2B62, 0x2B63, 0x2B64, 0x2B65, 0x2B66, 0x2B67, 0x2B68,
        0x2B69, 0x2B6A, 0x2B6B, 0x2B6C, 0x2B6D, 0x2B6E, 0x2B6F, 0x2B70, 0x2B71,
        0x2B72, 0x2B73, 0x2B74, 0x2B75, 0x2B76, 0x2B77, 0x2B78, 0x2B79, 0x2B7A,
        0x2B7B, 0x2B7C, 0x2B7D, 0x2B7E, 0x2B7F, 0x2B80, 0x2B81, 0x2B82, 0x2B83,
        0x2B84, 0x2B85, 0x2B86, 0x2B87, 0x2B88, 0x2B89, 0x2B8A, 0x2B8B, 0x2B8C,
        0x2B8D, 0x2B8E, 0x2B8F, 0x2B90, 0x2B91, 0x2B92, 0x2B93, 0x2B94, 0x2B95,
        0x2B96, 0x2B97, 0x2B98, 0x2B99, 0x2B9A, 0x2B9B, 0x2B9C, 0x2B9D, 0x2B9E,
        0x2B9F, 0x2BA0, 0x2BA1, 0x2BA2, 0x2BA3, 0x2BA4, 0x2BA5, 0x2BA6, 0x2BA7,
        0x2BA8, 0x2BA9, 0x2BAA, 0x2BAB, 0x2BAC, 0x2BAD, 0x2BAE, 0x2BAF, 0x2BB0,
        0x2BB1, 0x2BB2, 0x2BB3, 0x2BB4, 0x2BB5, 0x2BB6, 0x2BB7, 0x2BB8, 0x2BB9,
        0x2BBA, 0x2BBB, 0x2BBC, 0x2BBD, 0x2BBE, 0x2BBF, 0x2BC0, 0x2BC1, 0x2BC2,
        0x2BC3, 0x2BC4, 0x2BC5, 0x2BC6, 0x2BC7, 0x2BC8, 0x2BC9, 0x2BCA, 0x2BCB,
        0x2BCC, 0x2BCD, 0x2BCE, 0x2BCF, 0x2BD0, 0x2BD1, 0x2BD2, 0x2BD3, 0x2BD4,
        0x2BD5, 0x2BD6, 0x2BD7, 0x2BD8, 0x2BD9, 0x2BDA, 0x2BDB, 0x2BDC, 0x2BDD,
        0x2BDE, 0x2BDF, 0x2BE0, 0x2BE1, 0x2BE2, 0x2BE3, 0x2BE4, 0x2BE5, 0x2BE6,
        0x2BE7, 0x2BE8, 0x2BE9, 0x2BEA, 0x2BEB, 0x2BEC, 0x2BED, 0x2BEE, 0x2BEF,
        0x2BF0, 0x2BF1, 0x2BF2, 0x2BF3, 0x2BF4, 0x2BF5, 0x2BF6, 0x2BF7, 0x2BF8,
        0x2BF9, 0x2BFA, 0x2BFB, 0x2BFC, 0x2BFD, 0x2BFE, 0x2BFF, 0x2E00, 0x2E01,
        0x2E02, 0x2E03, 0x2E04, 0x2E05, 0x2E06, 0x2E07, 0x2E08, 0x2E09, 0x2E0A,
        0x2E0B, 0x2E0C, 0x2E0D, 0x2E0E, 0x2E0F, 0x2E10, 0x2E11, 0x2E12, 0x2E13,
        0x2E14, 0x2E15, 0x2E16, 0x2E17, 0x2E18, 0x2E19, 0x2E1A, 0x2E1B, 0x2E1C,
        0x2E1D, 0x2E1E, 0x2E1F, 0x2E20, 0x2E21, 0x2E22, 0x2E23, 0x2E24, 0x2E25,
        0x2E26, 0x2E27, 0x2E28, 0x2E29, 0x2E2A, 0x2E2B, 0x2E2C, 0x2E2D, 0x2E2E,
        0x2E2F, 0x2E30, 0x2E31, 0x2E32, 0x2E33, 0x2E34, 0x2E35, 0x2E36, 0x2E37,
        0x2E38, 0x2E39, 0x2E3A, 0x2E3B, 0x2E3C, 0x2E3D, 0x2E3E, 0x2E3F, 0x2E40,
        0x2E41, 0x2E42, 0x2E43, 0x2E44, 0x2E45, 0x2E46, 0x2E47, 0x2E48, 0x2E49,
        0x2E4A, 0x2E4B, 0x2E4C, 0x2E4D, 0x2E4E, 0x2E4F, 0x2E50, 0x2E51, 0x2E52,
        0x2E53, 0x2E54, 0x2E55, 0x2E56, 0x2E57, 0x2E58, 0x2E59, 0x2E5A, 0x2E5B,
        0x2E5C, 0x2E5D, 0x2E5E, 0x2E5F, 0x2E60, 0x2E61, 0x2E62, 0x2E63, 0x2E64,
        0x2E65, 0x2E66, 0x2E67, 0x2E68, 0x2E69, 0x2E6A, 0x2E6B, 0x2E6C, 0x2E6D,
        0x2E6E, 0x2E6F, 0x2E70, 0x2E71, 0x2E72, 0x2E73, 0x2E74, 0x2E75, 0x2E76,
        0x2E77, 0x2E78, 0x2E79, 0x2E7A, 0x2E7B, 0x2E7C, 0x2E7D, 0x2E7E, 0x2E7F,
        0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300A, 0x300B, 0x300C, 0x300D,
        0x300E, 0x300F, 0x3010, 0x3011, 0x3012, 0x3013, 0x3014, 0x3015, 0x3016,
        0x3017, 0x3018, 0x3019, 0x301A, 0x301B, 0x301C, 0x301D, 0x301E, 0x301F,
        0x3020, 0x3030, 0xFD3E, 0xFD3F, 0xFE45, 0xFE46,
    )
)
# fmt: on


def rstrip_tuple(things: typing.Tuple, element_to_strip) -> typing.Tuple:
    for i in range(len(things) - 1, 0, -1):
        if things[i] != element_to_strip:
            return things[: i + 1]
    return things


def test_is_id_start() -> None:
    # L
    assert is_id_start(0x61)  # LATIN SMALL LETTER A (Lu)
    assert is_id_start(0x41)  # LATIN CAPITAL LETTER A (Ll)
    assert is_id_start(0x00AA)  # FEMININE ORDINAL INDICATOR (Lo)
    assert is_id_start(0x02B0)  # MODIFIER LETTER SMALL H (Lm)
    assert is_id_start(
        0x1F88
    )  # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI (Lt)
    assert is_id_start(0x08BE)  # ARABIC LETTER PEH WITH SMALL V (Lo)

    # Nl
    assert is_id_start(0x2160)  # ROMAN NUMERAL ONE (Nl)

    # Other_ID_Start
    assert is_id_start(0x1885)  # MONGOLIAN LETTER ALI GALI BALUDA
    assert is_id_start(0x1886)  # MONGOLIAN LETTER ALI GALI THREE BALUDA
    assert is_id_start(0x2118)  # SCRIPT CAPITAL P
    assert is_id_start(0x212E)  # ESTIMATED SYMBOL
    assert is_id_start(0x309B)  # KATAKANA-HIRAGANA VOICED SOUND MARK
    assert is_id_start(0x309C)  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

    # Pattern_Syntax (disallowed)
    for code_point in PATTERN_SYNTAX_CODE_POINTS:
        assert not is_id_start(code_point), f"{code_point:#x}"

    # Pattern_White_Space (disallowed)
    for code_point in PATTERN_WHITE_SPACE_CODE_POINTS:
        assert not is_id_start(code_point), f"{code_point:#x}"

    # No (disallowed)
    assert not is_id_start(0x2460)  # CIRCLED DIGIT ONE (No)

    # Pc (disallowed)
    assert not is_id_start(0x005F)  # LOW LINE (Pc)
    assert not is_id_start(0x203F)  # UNDERTIE (Pc)

    # Pd (disallowed)
    assert not is_id_start(0x1400)  # CANADIAN SYLLABICS HYPHEN (Pd)

    # Sc (disallowed)
    assert not is_id_start(0x20A0)  # EURO-CURRENCY SIGN (Sc)
    assert not is_id_start(0x0024)  # DOLLAR SIGN (Sc)

    # Me (disallowed)
    assert not is_id_start(0x20DD)  # COMBINING ENCLOSING CIRCLE (Me)

    # Mn (disallowed)
    assert not is_id_start(0x20F0)  # COMBINING ASTERISK ABOVE (Mn)

    # Nd (disallowed)
    assert not is_id_start(0xA627)  # VAI DIGIT SEVEN (Nd)

    # Cf (disallowed)
    assert not is_id_start(0x200C)  # ZERO WIDTH NON-JOINER (Cf)
    assert not is_id_start(0x200D)  # ZERO WIDTH JOINER (Cf)
    assert not is_id_start(0x202C)  # POP DIRECTIONAL FORMATTING (Cf)

    # Other_ID_Continue (disallowed)
    for code_point in OTHER_ID_CONTINUE_CODE_POINTS:
        assert not is_id_start(code_point), f"{code_point:#x}"


def test_is_id_continue() -> None:
    # L
    assert is_id_continue(0x61)  # LATIN SMALL LETTER A (Lu)
    assert is_id_continue(0x41)  # LATIN CAPITAL LETTER A (Ll)
    assert is_id_continue(0x00AA)  # FEMININE ORDINAL INDICATOR (Lo)
    assert is_id_continue(0x02B0)  # MODIFIER LETTER SMALL H (Lm)
    assert is_id_continue(
        0x1F88
    )  # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI (Lt)

    # Nl
    assert is_id_continue(0x2160)  # ROMAN NUMERAL ONE (Nl)

    # Other_ID_Start
    assert is_id_continue(0x1885)  # MONGOLIAN LETTER ALI GALI BALUDA
    assert is_id_continue(0x1886)  # MONGOLIAN LETTER ALI GALI THREE BALUDA
    assert is_id_continue(0x2118)  # SCRIPT CAPITAL P
    assert is_id_continue(0x212E)  # ESTIMATED SYMBOL
    assert is_id_continue(0x309B)  # KATAKANA-HIRAGANA VOICED SOUND MARK
    assert is_id_continue(0x309C)  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

    # Mn
    assert is_id_continue(0x20F0)  # COMBINING ASTERISK ABOVE (Mn)

    # Mc
    assert is_id_continue(0x1C26)  # LEPCHA VOWEL SIGN AA (Mc)

    # Nd
    assert is_id_continue(0xA627)  # VAI DIGIT SEVEN (Nd)

    # Pc
    assert is_id_continue(0x005F)  # LOW LINE (Pc)
    assert is_id_continue(0x203F)  # UNDERTIE (Pc)

    # Other_ID_Continue
    for code_point in OTHER_ID_CONTINUE_CODE_POINTS:
        assert is_id_continue(code_point), f"{code_point:#x}"

    # Pattern_Syntax (disallowed)
    for code_point in PATTERN_SYNTAX_CODE_POINTS:
        assert not is_id_continue(code_point), f"{code_point:#x}"

    # Pattern_White_Space (disallowed)
    for code_point in PATTERN_WHITE_SPACE_CODE_POINTS:
        assert not is_id_continue(code_point), f"{code_point:#x}"

    # No (disallowed)
    assert not is_id_continue(0x2460)  # CIRCLED DIGIT ONE (No)

    # Pd (disallowed)
    assert not is_id_continue(0x1400)  # CANADIAN SYLLABICS HYPHEN (Pd)

    # Sc (disallowed)
    assert not is_id_continue(0x20A0)  # EURO-CURRENCY SIGN (Sc)
    assert not is_id_continue(0x0024)  # DOLLAR SIGN (Sc)

    # Me (disallowed)
    assert not is_id_continue(0x20DD)  # COMBINING ENCLOSING CIRCLE (Me)

    # Cf (disallowed)
    assert not is_id_continue(0x200C)  # ZERO WIDTH NON-JOINER (Cf)
    assert not is_id_continue(0x200D)  # ZERO WIDTH JOINER (Cf)
    assert not is_id_continue(0x202C)  # POP DIRECTIONAL FORMATTING (Cf)


def test_is_js_identifier_start() -> None:
    # Extra allowed characters
    assert is_js_identifier_start(0x0024)  # DOLLAR SIGN (Sc)
    assert is_js_identifier_start(0x005F)  # LOW LINE (Pc)

    # L
    assert is_js_identifier_start(0x61)  # LATIN SMALL LETTER A (Lu)
    assert is_js_identifier_start(0x41)  # LATIN CAPITAL LETTER A (Ll)
    assert is_js_identifier_start(0x00AA)  # FEMININE ORDINAL INDICATOR (Lo)
    assert is_js_identifier_start(0x02B0)  # MODIFIER LETTER SMALL H (Lm)
    assert is_js_identifier_start(
        0x1F88
    )  # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI (Lt)

    # Nl
    assert is_js_identifier_start(0x2160)  # ROMAN NUMERAL ONE (Nl)

    # Other_ID_Start
    assert is_js_identifier_start(0x1885)  # MONGOLIAN LETTER ALI GALI BALUDA
    assert is_js_identifier_start(0x1886)  # MONGOLIAN LETTER ALI GALI THREE BALUDA
    assert is_js_identifier_start(0x2118)  # SCRIPT CAPITAL P
    assert is_js_identifier_start(0x212E)  # ESTIMATED SYMBOL
    assert is_js_identifier_start(0x309B)  # KATAKANA-HIRAGANA VOICED SOUND MARK
    assert is_js_identifier_start(0x309C)  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

    # Pattern_Syntax (disallowed, except for DOLLAR SIGN)
    for code_point in PATTERN_SYNTAX_CODE_POINTS - {ord("$")}:
        assert not is_js_identifier_start(code_point), f"{code_point:#x}"

    # Pattern_White_Space (disallowed)
    for code_point in PATTERN_WHITE_SPACE_CODE_POINTS:
        assert not is_js_identifier_start(code_point), f"{code_point:#x}"

    # No (disallowed)
    assert not is_js_identifier_start(0x2460)  # CIRCLED DIGIT ONE (No)

    # Pc (disallowed)
    assert not is_js_identifier_start(0x203F)  # UNDERTIE (Pc)

    # Pd (disallowed)
    assert not is_js_identifier_start(0x1400)  # CANADIAN SYLLABICS HYPHEN (Pd)

    # Sc (disallowed)
    assert not is_js_identifier_start(0x20A0)  # EURO-CURRENCY SIGN (Sc)

    # Me (disallowed)
    assert not is_js_identifier_start(0x20DD)  # COMBINING ENCLOSING CIRCLE (Me)

    # Mn (disallowed)
    assert not is_js_identifier_start(0x20F0)  # COMBINING ASTERISK ABOVE (Mn)

    # Nd (disallowed)
    assert not is_js_identifier_start(0xA627)  # VAI DIGIT SEVEN (Nd)

    # Cf (disallowed)
    assert not is_js_identifier_start(0x200C)  # ZERO WIDTH NON-JOINER (Cf)
    assert not is_js_identifier_start(0x200D)  # ZERO WIDTH JOINER (Cf)
    assert not is_js_identifier_start(0x202C)  # POP DIRECTIONAL FORMATTING (Cf)

    # Other_ID_Continue (disallowed)
    for code_point in OTHER_ID_CONTINUE_CODE_POINTS:
        assert not is_js_identifier_start(code_point), f"{code_point:#x}"


def test_is_js_identifier_part() -> None:
    # Extra allowed characters
    assert is_js_identifier_part(0x0024)  # DOLLAR SIGN (Sc)
    assert is_js_identifier_part(0x200C)  # ZERO WIDTH NON-JOINER (Cf)
    assert is_js_identifier_part(0x200D)  # ZERO WIDTH JOINER (Cf)

    # L
    assert is_js_identifier_part(0x61)  # LATIN SMALL LETTER A (Lu)
    assert is_js_identifier_part(0x41)  # LATIN CAPITAL LETTER A (Ll)
    assert is_js_identifier_part(0x00AA)  # FEMININE ORDINAL INDICATOR (Lo)
    assert is_js_identifier_part(0x02B0)  # MODIFIER LETTER SMALL H (Lm)
    assert is_js_identifier_part(
        0x1F88
    )  # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI (Lt)

    # Nl
    assert is_js_identifier_part(0x2160)  # ROMAN NUMERAL ONE (Nl)

    # Other_ID_Start
    assert is_js_identifier_part(0x1885)  # MONGOLIAN LETTER ALI GALI BALUDA
    assert is_js_identifier_part(0x1886)  # MONGOLIAN LETTER ALI GALI THREE BALUDA
    assert is_js_identifier_part(0x2118)  # SCRIPT CAPITAL P
    assert is_js_identifier_part(0x212E)  # ESTIMATED SYMBOL
    assert is_js_identifier_part(0x309B)  # KATAKANA-HIRAGANA VOICED SOUND MARK
    assert is_js_identifier_part(0x309C)  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

    # Mn
    assert is_js_identifier_part(0x20F0)  # COMBINING ASTERISK ABOVE (Mn)

    # Mc
    assert is_js_identifier_part(0x1C26)  # LEPCHA VOWEL SIGN AA (Mc)

    # Nd
    assert is_js_identifier_part(0xA627)  # VAI DIGIT SEVEN (Nd)

    # Pc
    assert is_js_identifier_part(0x005F)  # LOW LINE (Pc)
    assert is_js_identifier_part(0x203F)  # UNDERTIE (Pc)

    # Other_ID_Continue
    for code_point in OTHER_ID_CONTINUE_CODE_POINTS:
        assert is_js_identifier_part(code_point), f"{code_point:#x}"

    # Pattern_Syntax (disallowed, except for DOLLAR SIGN)
    for code_point in PATTERN_SYNTAX_CODE_POINTS - {ord("$")}:
        assert not is_js_identifier_part(code_point), f"{code_point:#x}"

    # Pattern_White_Space (disallowed)
    for code_point in PATTERN_WHITE_SPACE_CODE_POINTS:
        assert not is_js_identifier_part(code_point), f"{code_point:#x}"

    # No (disallowed)
    assert not is_js_identifier_part(0x2460)  # CIRCLED DIGIT ONE (No)

    # Pd (disallowed)
    assert not is_js_identifier_part(0x1400)  # CANADIAN SYLLABICS HYPHEN (Pd)

    # Sc (disallowed)
    assert not is_js_identifier_part(0x20A0)  # EURO-CURRENCY SIGN (Sc)

    # Me (disallowed)
    assert not is_js_identifier_part(0x20DD)  # COMBINING ENCLOSING CIRCLE (Me)

    # Cf (disallowed)
    assert not is_js_identifier_part(0x202C)  # POP DIRECTIONAL FORMATTING (Cf)


def chunk(items: typing.Sequence, chunk_size: int) -> typing.Iterable[typing.Sequence]:
    for i in range(0, len(items), chunk_size):
        yield items[i : i + chunk_size]


def dump_bit_table_to_file(
    bits: typing.Sequence[bool], file, indentation: bytes = b""
) -> None:
    bytes = []
    for byte_index in range(0, len(bits), BITS_PER_BYTE):
        byte = 0
        for bit_in_byte in range(BITS_PER_BYTE):
            try:
                bit = bits[byte_index + bit_in_byte]
            except IndexError:
                bit = 0
            byte |= bit << bit_in_byte
        bytes.append(byte)

    dump_integer_table_to_file(
        bytes,
        file=file,
        indentation=indentation,
    )


def dump_integer_table_to_file(
    integers: typing.Iterable[int],
    file,
    format: str = "#04x",
    integers_per_line: int = 8,
    indentation: bytes = b"",
) -> None:
    integers_written_on_line = 0
    for integer in integers:
        if integers_written_on_line >= integers_per_line:
            file.write(b"\n")
            integers_written_on_line = 0
        if integers_written_on_line > 0:
            file.write(b" ")
        else:
            file.write(indentation)
        file.write(f"{integer:{format}},".encode())
        integers_written_on_line += 1


def dump_bit_table_to_bytes(
    bits: typing.Sequence[bool], indentation: bytes = b""
) -> bytes:
    file = io.BytesIO()
    dump_bit_table_to_file(bits, file, indentation=indentation)
    return file.getvalue()


def test_dump_bit_table() -> None:
    assert dump_bit_table_to_bytes(()) == b""
    assert dump_bit_table_to_bytes((1, 0, 1, 0, 1, 0, 1, 0)) == b"0x55,"
    # fmt: off
    assert dump_bit_table_to_bytes((
        1, 1, 1, 1, 0, 0, 0, 0,
        0, 0, 0, 0, 1, 1, 1, 1,
    )) == b"0x0f, 0xf0,"
    # fmt: on

    # Dumping zero-pads missing bits:
    assert (
        dump_bit_table_to_bytes(
            (
                1,
                0,
                1,
                0,
                1,
            )
        )
        == b"0x15,"
    )

    # Dumping should line break after 8 bytes:
    # fmt: off
    result = dump_bit_table_to_bytes(
        [
            int(char)
            for char in
            "10000000"  # 0x01
            "01000000"  # 0x02
            "11000000"  # 0x03
            "00100000"  # 0x04
            "10100000"  # 0x05
            "01100000"  # 0x06
            "11100000"  # 0x07
            "00010000"  # 0x08
            "10010000"  # 0x09
            "01010000"  # 0x0a
            "11010000"  # 0x0b
            "00110000"  # 0x0c
            "10110000"  # 0x0d
            "01110000"  # 0x0e
            "11110000"  # 0x0f
            "00001000"  # 0x10
            "10001000"  # 0x11
            "01001000"  # 0x12
            "11001000"  # 0x13
            "00101000"  # 0x14
        ]
    )
    assert result == b"""\
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n\
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,\n\
0x11, 0x12, 0x13, 0x14,""", result
    # fmt: on

    # Dumping should indent at the beginning of each line:
    # fmt: off
    result = dump_bit_table_to_bytes(
        [
            int(char)
            for char in
            "10000000"  # 0x01
            "01000000"  # 0x02
            "11000000"  # 0x03
            "00100000"  # 0x04
            "10100000"  # 0x05
            "01100000"  # 0x06
            "11100000"  # 0x07
            "00010000"  # 0x08
            "10010000"  # 0x09
        ],
        indentation=b"....",
    )
    assert result == b"""\
....0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n\
....0x09,""", result
    # fmt: on


def test_rstrip_tuple() -> None:
    assert rstrip_tuple((), 3) == ()
    assert rstrip_tuple((1, 2), 3) == (1, 2)
    assert rstrip_tuple((1, 2, 3), 3) == (1, 2)
    assert rstrip_tuple((3, 2, 1), 3) == (3, 2, 1)
    assert rstrip_tuple((1, 3, 1, 3, 3, 3, 3), 3) == (1, 3, 1)


if __name__ == "__main__":
    main()

# quick-lint-js finds bugs in JavaScript programs.
# Copyright (C) 2020  Matthew "strager" Glazar
#
# This file is part of quick-lint-js.
#
# quick-lint-js is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# quick-lint-js is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with quick-lint-js.  If not, see <https://www.gnu.org/licenses/>.
